#include "BufferInterface.hpp"

namespace Buffer {

constexpr GLuint DEFAULT_BUFFER = 0;

BufferBase::BufferBase(const BufferType &type)
    : mapped(false), type(type), id(0), access_modified(0) {}

void BufferBase::Create() { glCreateBuffers(1, &id); }

GLuint BufferBase::GetID() const { return id; }

void BufferBase::BindBuffer() { glBindBuffer(GetGLBufferType(type), id); }

void BufferBase::UnBind() { glBindBuffer(GetGLBufferType(type), DEFAULT_BUFFER); }

void BufferBase::AssignData(const BufferFormat &format, const void *data) const {
  glNamedBufferData(id, format.bufferBytes * GetDataBytes(format.dataType), data,
                    format.hint);
}

bool BufferBase::Mapped() const { return mapped; }

void BufferBase::SetAccessFlagBits(GLbitfield flags) { access_modified = flags; }

void *BufferBase::SampleBuffer(GLintptr offset, GLsizeiptr length) {
  // Has been mapped
  if (mapped) {
    return nullptr;
  } else {
    mapped = true;
  }
  return glMapNamedBufferRange(id, offset, length, access_modified);
}

BufferBase::BufferAccessState BufferBase::EndSample(GLintptr offset, GLsizeiptr length) {
  if (mapped) {
    mapped = false;
    if (access_modified & GL_MAP_FLUSH_EXPLICIT_BIT) {
      glFlushMappedNamedBufferRange(id, offset, length);
    }
    return glUnmapNamedBuffer(id) ? BufferAccessState::Success : BufferAccessState::Error;
  }
  return BufferAccessState::Error;
}

void *BufferBase::SampleAllBuffer(GLenum access) {
  if (mapped) {
    return nullptr;
  } else {
    mapped = true;
  }
  return glMapNamedBuffer(id, access);
}

BufferBase::BufferAccessState BufferBase::EndSampleAll() {
  if (mapped) {
    mapped = false;
    return glUnmapNamedBuffer(id) ? BufferAccessState::Success : BufferAccessState::Error;
  }
  return BufferAccessState::Error;
}

void BufferBase::Delete() {
  glDeleteBuffers(1, &id);
  id = 0;
}

BufferBase::~BufferBase() {
  Delete();
  access_modified = 0;
  mapped = false;
}

} // namespace Buffer